﻿/*
Copyright (c) 2012 Maksim Gordeev

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

 */

/*
 * Сделано в SharpDevelop.
 * Пользователь: maxxadm
 * Дата: 05.06.2012
 * Время: 19:11
 * 
 * Для изменения этого шаблона используйте Сервис | Настройка | Кодирование | Правка стандартных заголовков.
 */
using System;
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;

namespace jellybird.core
{
	/*
	 * https://dev.twitter.com/docs/platform-objects/tweets
	 */
	
	/// <summary>
	/// Description of TwTweet.
	/// </summary>
	public class TwTweet:TwObjectBase,INotifyPropertyChanged
	{
		public TwTweet()
		{
		}
		
		public override string ToString()
		{
			return string.Format
				("@{0}",
				 User.ScreenName);
		}

		#region prop changed implementation
		public event PropertyChangedEventHandler PropertyChanged;
		
		protected void On_property_changed(string property_name)
		{
			if(PropertyChanged==null)
				return;
			
			System.Windows.Threading.Dispatcher dispatcher=
				System.Windows.Application.Current.Dispatcher;

			PropertyChangedEventArgs e=new PropertyChangedEventArgs(property_name);
			if(dispatcher.CheckAccess())
			{
				PropertyChanged.Invoke(this,e);
			}
			else
			{
				Delegate[] invoke_list=PropertyChanged.GetInvocationList();
				foreach(Delegate handler in invoke_list)
				{
					dispatcher.BeginInvoke(handler,this,e);
				}
			}
		}
		#endregion

		#region spec props
		private List<TwMediaInfo> m_MediaInfo;
		[JsonIgnore()]
		public List<TwMediaInfo> MediaInfo
		{
			get
			{
				if(m_MediaInfo==null)
				{
					m_MediaInfo=new List<TwMediaInfo>();
					
					if(Entities!=null)
					{
						if(Entities.Media!=null)
						{
							foreach(var ent_media in Entities.Media)
							{
								TwMediaInfo new_info=TwMediaInfo.FromTwMediaEntity(ent_media);
								if(new_info!=null)
									m_MediaInfo.Add(new_info);
							}
						}
						if(Entities.Urls!=null)
						{
							foreach(var ent_url in Entities.Urls)
							{
								TwMediaInfo new_info=TwMediaInfo.FromUri(ent_url);
								if(new_info!=null)
									m_MediaInfo.Add(new_info);
							}
						}
					}
				}
				return m_MediaInfo;
			}
		}

		[JsonIgnore()]
		public bool ContentsMedia
		{
			get
			{
				if(Entities==null)
					return false;
				
				if ((Entities.Media != null) && (Entities.Media.Count > 0))
					return true;

				if ((Entities.Urls != null) && (Entities.Urls.Count > 0))
				{
					//проверяем ссылки
					//TODO: в отдельный метод
					foreach (var url_ent in Entities.Urls)
					{
						//здесь проверить ExpandedUrl -
						//поод ли на кртинку
						if (TwMediaInfo.Supported(url_ent))
							return true;
					}
				}
				return false;
			}
		}

		private bool m_GapDetected=false;
		/// <summary>
		/// Возможен разрыв перед ним
		/// </summary>
		[JsonIgnoreAttribute()]
		public bool GapDetected
		{
			//возможно, не совсем потокобезопасно
			get
			{
				return m_GapDetected;
			}
			set
			{
				if(m_GapDetected!=value)
				{
					m_GapDetected=value;
					On_property_changed("GapDetected");
				}
			}
		}
		#endregion

		#region props from json
		TwTweet retweetedStatus;
		/*
		 * В документации нет,
		 * но вроде бы должен быть
		 */
		[JsonProperty("retweeted_status")]
		public TwTweet RetweetedStatus
		{
			get { return retweetedStatus; }
			set { retweetedStatus = value; }
		}
		

		string annotations;
		
		/// <summary>
		/// Unused. Future/beta home for status annotations.
		/// </summary>
		[JsonProperty("annotations")]
		public string Annotations {
			get	{ return annotations; }
			set { annotations = value; }
		}
		
		[JsonIgnoreAttribute()]
		public string AnnotationsOriginal
		{
			get
			{
				return RetweetedStatus==null?Annotations:RetweetedStatus.Annotations;
			}
		}
		
		List<TwContributor> contributors;
		
		/// <summary>
		/// An collection of brief user objects (usually only one)
		///	indicating users who contributed to the authorship of the tweet,
		///	on behalf of the official tweet author.
		/// </summary>
		[JsonProperty("contributors")]
		public List<TwContributor> Contributors {
			get { return contributors; }
			set { contributors = value; }
		}
		
		[JsonIgnoreAttribute()]
		public List<TwContributor> ContributorsOriginal
		{
			get
			{
				return RetweetedStatus==null?Contributors:RetweetedStatus.Contributors;
			}
		}
		
		TwCoordinates coordinates;
		
		/// <summary>
		/// Nullable. Represents the geographic location of this Tweet
		///	as reported by the user or client application.
		///	The inner coordinates array is formatted as
		///	geoJSON (longitude first, then latitude).
		/// </summary>
		[JsonProperty("coordinates")]
		public TwCoordinates Coordinates {
			get { return coordinates; }
			set { coordinates = value; }
		}
		
		[JsonIgnore()]
		public TwCoordinates CoordinatesOriginal
		{
			get
			{
				return RetweetedStatus==null?Coordinates:RetweetedStatus.Coordinates;
			}
		}
		
		string createdAtStr;
		
		/// <summary>
		/// Local time when this Tweet was created.
		/// </summary>
		[JsonProperty("created_at")]
		public string CreatedAtStr {
			get { return createdAtStr; }
			set
			{
				if(Helper.TryParseFromTw(value,ref createdAt))
					createdAtStr = value;
				else
					throw new FormatException(string.Format("Cannot parse time string '{0}'",value));
			}
		}
		
		[JsonIgnoreAttribute()]
		public string CreatedAtStrOriginal
		{
			get{return RetweetedStatus==null?CreatedAtStr:RetweetedStatus.CreatedAtStr;}
		}
		
		DateTime createdAt;
		
		/// <summary>
		/// Local time when this Tweet was created.
		/// </summary>
		[JsonIgnore()]
		public DateTime CreatedAt {
			get { return createdAt; }
			set
			{
				createdAt=value;
				// и в строку:
				createdAtStr=Helper.TwTimeFromDatetime(value);
			}
		}
		
		[JsonIgnore()]
		public DateTime CreatedAtOriginal
		{
			get{return RetweetedStatus==null?CreatedAt:RetweetedStatus.CreatedAt;}
		}
		
		TwUserRetweetDetails currentUserRetweet;
		
		/// <summary>
		/// Perspectival. Only surfaces on methods supporting
		///	the include_my_retweet parameter, when set to true.
		///	Details the Tweet ID of the user's own retweet
		///	(if existent) of this Tweet.
		/// </summary>
		[JsonProperty("current_user_retweet")]
		public TwUserRetweetDetails CurrentUserRetweet {
			get { return currentUserRetweet; }
			set { currentUserRetweet = value; }
		}
		
		TwEntities entities;
		
		/// <summary>
		/// Entities which have been parsed out of the text of the Tweet.
		/// </summary>
		[JsonProperty("entities")]
		public TwEntities Entities {
			get { return entities; }
			set { entities = value; }
		}
		
		[JsonIgnore()]
		public TwEntities EntitiesOriginal
		{
			get{return RetweetedStatus==null?Entities:RetweetedStatus.Entities;}
		}
		
		bool? favorited;
		
		/// <summary>
		/// Nullable. Perspectival. Indicates whether this Tweet
		///	has been favorited by the authenticating user.
		/// </summary>
		[JsonProperty("favorited")]
		public bool? Favorited {
			get { return favorited; }
			set { favorited = value; }
		}
		
		object geo;
		
		/// <summary>
		/// Deprecated. Nullable. Use the "coordinates" field instead.
		/// </summary>
		[Obsolete("Use the 'coordinates' field instead.")]
		[JsonProperty("geo")]
		public object Geo {
			get { return geo; }
			set { geo = value; }
		}
		
		[JsonIgnore()]
		public object GeoOriginal
		{
			get{return RetweetedStatus==null?Geo:RetweetedStatus.Geo;}
		}
		
		long id;
		
		/// <summary>
		/// The integer representation of the unique identifier for this Tweet.
		///	This number is greater than 53 bits and some programming languages
		///	may have difficulty/silent defects in interpreting it. Using a signed
		///	64 bit integer for storing this identifier is safe. Use id_str for
		///	fetching the identifier to stay on the safe side.
		/// </summary>
		[JsonProperty("id")]
		public long Id {
			get { return id; }
			set { id = value; }
		}
		
		string idString;
		
		/// <summary>
		/// The string representation of the unique identifier for this Tweet.
		///	Implementations should use this rather than the large integer in id.
		/// </summary>
		[JsonProperty("id_str")]
		public string IdString {
			get { return idString; }
			set { idString = value; }
		}
		
		string inReplyTo_ScreenName;
		
		/// <summary>
		/// Nullable. If the represented Tweet is a reply, this field will contain
		///	the screen name of the original Tweet's author.
		/// </summary>
		[JsonProperty("in_reply_to_screen_name")]
		public string InReplyTo_ScreenName {
			get { return inReplyTo_ScreenName; }
			set { inReplyTo_ScreenName = value; }
		}
		
		[JsonIgnore()]
		public string InReplyTo_ScreenName_Original
		{
			get{return RetweetedStatus==null?InReplyTo_ScreenName:RetweetedStatus.InReplyTo_ScreenName;}
		}
		
		long? inReplyTo_StatusId;
		
		/// <summary>
		/// Nullable. If the represented Tweet is a reply, this field will contain
		///	the integer representation of the original Tweet's ID.
		/// </summary>
		[JsonProperty("in_reply_to_status_id")]
		public long? InReplyTo_StatusId {
			get { return inReplyTo_StatusId; }
			set { inReplyTo_StatusId = value; }
		}
		
		[JsonIgnore()]
		public long? InReplyTo_StatusId_Original
		{
			get{return RetweetedStatus==null?InReplyTo_StatusId:RetweetedStatus.InReplyTo_StatusId;}
		}
		
		string inReplyTo_StatusIdString;
		
		/// <summary>
		/// Nullable. If the represented Tweet is a reply, this field will contain
		///	the string representation of the original Tweet's ID.
		/// </summary>
		[JsonProperty("in_reply_to_status_id_str")]
		public string InReplyTo_StatusIdString {
			get { return inReplyTo_StatusIdString; }
			set { inReplyTo_StatusIdString = value; }
		}
		
		long? inReplyTo_UserId;
		
		/// <summary>
		/// Nullable. If the represented Tweet is a reply, this field will contain
		///	the integer representation of the original Tweet's author ID.
		/// </summary>
		[JsonProperty("in_reply_to_user_id")]
		public long? InReplyTo_UserId {
			get { return inReplyTo_UserId; }
			set { inReplyTo_UserId = value; }
		}
		
		[JsonIgnore()]
		public long? InReplyTo_UserId_Original
		{
			get{return RetweetedStatus==null?InReplyTo_UserId:RetweetedStatus.InReplyTo_UserId;}
		}
		
		string inReplyTo_UserIdString;
		
		/// <summary>
		/// Nullable. If the represented Tweet is a reply, this field will contain
		///	the string representation of the original Tweet's author ID.
		/// </summary>
		[JsonProperty("in_reply_to_user_id_str")]
		public string InReplyTo_UserIdString {
			get { return inReplyTo_UserIdString; }
			set { inReplyTo_UserIdString = value; }
		}
		
		TwPlace place;
		
		/// <summary>
		/// Nullable. When present, indicates that the tweet is associated
		///	(but not necessarily originating from) a Place.
		/// </summary>
		[JsonProperty("place")]
		public TwPlace Place {
			get { return place; }
			set { place = value; }
		}
		
		[JsonIgnore()]
		public TwPlace  PlaceOriginal
		{
			get{return RetweetedStatus==null?Place:RetweetedStatus.Place;}
		}
		
		bool? possiblySensitive;
		
		/// <summary>
		/// Nullable. This field only surfaces when a tweet contains a link.
		///	The meaning of the field doesn't pertain to the tweet content
		///	itself, but instead it is an indicator that the URL contained
		///	in the tweet may contain content or media identified
		///	as sensitive content.
		/// </summary>
		[JsonProperty("possibly_sensitive")]
		public bool? PossiblySensitive {
			get { return possiblySensitive; }
			set { possiblySensitive = value; }
		}
		
		[JsonIgnore()]
		public bool? PossibleSensitiveOriginal
		{
			get{return RetweetedStatus==null?PossiblySensitive:RetweetedStatus.PossiblySensitive;}
		}
		
		int retweetCount;
		
		/// <summary>
		/// Number of times this Tweet has been retweeted. This field is no
		///	longer capped at 99 and will not turn into a String for "100+"
		/// </summary>
		[JsonProperty("retweet_count")]
		public int RetweetCount {
			get { return retweetCount; }
			set { retweetCount = value; }
		}
		
		[JsonIgnore()]
		public int RetweetCountOriginal
		{
			get{return RetweetedStatus==null?RetweetCount:RetweetedStatus.RetweetCount;}
		}
		
		bool isRetweeted;
		
		/// <summary>
		/// Perspectival. Indicates whether this Tweet has been retweeted by
		///	the authenticating user.
		/// </summary>
		[JsonProperty("retweeted")]
		public bool IsRetweeted {
			get { return isRetweeted; }
			set { isRetweeted = value; }
		}
		
		string source;
		
		/// <summary>
		/// Utility used to post the Tweet, as an HTML-formatted string.
		///	Tweets from the Twitter website have a source value of web.
		/// </summary>
		/// <example>
		/// "source":
		/// "\u003Ca href=\"http:\/\/itunes.apple.com\/us\/app\/twitter\/id409789998?mt=12\"
		///	rel=\"nofollow\"\u003ETwitter for Mac\u003C\/a\u003E"
		/// </example>
		[JsonProperty("source")]
		public string Source {
			get { return source; }
			set { source = value; }
		}
		
		string text;
		
		/// <summary>
		/// The actual UTF-8 text of the status update.
		/// </summary>
		[JsonProperty("text")]
		public string Text {
			get { return text; }
			set { text = value; }
		}
		
		[JsonIgnore()]
		public string TextOriginal
		{
			get{return RetweetedStatus==null?Text:RetweetedStatus.Text;}
		}
		
		bool truncated;
		
		/// <summary>
		/// Indicates whether the value of the text parameter was truncated,
		///	for example, as a result of a retweet exceeding the 140 character
		///	Tweet length. Truncated text will end in ellipsis, like this ...
		/// </summary>
		[JsonProperty("truncated")]
		public bool Truncated {
			get { return truncated; }
			set { truncated = value; }
		}
		
		[JsonIgnoreAttribute()]
		public bool TruncatedOriginal
		{
			get{return RetweetedStatus==null?Truncated:RetweetedStatus.Truncated;}
		}
		
		TwUser user;
		long user_id;
		/// <summary>
		/// The user who posted this Tweet. Perspectival attributes embedded
		///	within this object are unreliable.
		/// </summary>
		[JsonProperty("user")]
		public TwUser User
		{
			get
			{
				//во первых получим ссылку на нужный кэш
				TwUsersCache cache=TwUsersCache.Get(Credentials);
				
				if(cache==null)
				{
					//это бывает когда работает десериализатор
					//ещё когда ссылки на TwCredentials и RestcLIENT пусты
					return null;
				}
				
				if(user!=null)
				{
					//то есть, возможно ещё не в кэше
					//закидываем его туда
					/*
					#if DEBUG
					Helper.OnDebugMessage
						(string.Format
						 ("Call EnsureCache for {0}. cache_len={1}",
						  user.ScreenName,
						  cache.Count));
					#endif
					 */
					cache.EnsureCache(user);
					//и обнуляем чтобы в следующую сборку мусора
					//освободить память
					user=null;
				}
				
				TwUser cached_user=cache[user_id];
				return cached_user;
			}
			set
			{
				user = value;
				user_id=value.Id;
			}
		}
		
		
		
		[JsonIgnore()]
		public TwUser UserOriginal
		{
			get{return RetweetedStatus==null?User:RetweetedStatus.User;}
		}
		
		string withheldInCountries;
		
		/// <summary>
		/// When present, indicates a textual representation of the two-letter
		///	country codes this content is withheld from.
		/// </summary>
		/// <example>
		/// "withheld_in_countries": "GR, HK, MY"
		/// </example>
		[JsonProperty("withheld_in_countries")]
		public string WithheldInCountries {
			get { return withheldInCountries; }
			set { withheldInCountries = value; }
		}
		
		string withheldScope;
		
		/// <summary>
		/// When present, indicates whether the content being withheld
		///	is the "status" or a "user."
		/// </summary>
		[JsonProperty("withheld_scope")]
		public string WithheldScope {
			get { return withheldScope; }
			set { withheldScope = value; }
		}
		#endregion
	}
	
	[TypeConverter(typeof(ExpandableObjectConverter))]
	public class TwUserRetweetDetails
	{
		public TwUserRetweetDetails()
		{
		}
		
		long id;
		
		[JsonProperty("id")]
		public long Id {
			get { return id; }
			set { id = value; }
		}
		
		string idString;
		
		[JsonProperty("id_str")]
		public string IdString {
			get { return idString; }
			set { idString = value; }
		}
	}
}
